<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="O3HYg" id="O3HYg"><span data-lake-id="ufab67618" id="ufab67618">典型回答</span></h1>
  <p data-lake-id="u6da71294" id="u6da71294"><br></p>
  <p data-lake-id="ue2ea7db6" id="ue2ea7db6"><span data-lake-id="u8663b1a4" id="u8663b1a4">SQL注入是一种常见的网络安全漏洞，攻击者通过在应用程序的用户输入中插入恶意的SQL代码，试图欺骗数据库执行非预期的查询。</span></p>
  <p data-lake-id="u7056f207" id="u7056f207"><span data-lake-id="u8d686da3" id="u8d686da3">​</span><br></p>
  <p data-lake-id="u7160b138" id="u7160b138"><span data-lake-id="ua9f4ac35" id="ua9f4ac35">SQL注入导致对数据库的未经授权的访问、数据泄露、数据破坏，甚至完整的数据库被攻陷。</span></p>
  <p data-lake-id="u13538f50" id="u13538f50"><br></p>
  <p data-lake-id="ufbea0767" id="ufbea0767"><span data-lake-id="u2725bc08" id="u2725bc08">攻击者常常通过在用户输入中注入SQL代码，改变应用程序对数据库的查询语句，以实现他们的恶意目的。</span></p>
  <p data-lake-id="u70d94ce3" id="u70d94ce3"><span data-lake-id="ufdf86766" id="ufdf86766">​</span><br></p>
  <p data-lake-id="u3ba0280b" id="u3ba0280b"><span data-lake-id="u5c84a168" id="u5c84a168">假设有一个简单的登录系统，根据用户提供的用户名和密码进行身份验证。应用程序可能会使用以下类似的SQL查询来检查用户是否存在：</span></p>
  <p data-lake-id="u3a1301d9" id="u3a1301d9"><br></p>
  <pre lang="java"><code>
String query = "SELECT * FROM users 
WHERE username='" + userInputUsername + "' 
AND password='" + userInputPassword + "'";
</code></pre>
  <p data-lake-id="uf52c7b57" id="uf52c7b57"><br></p>
  <p data-lake-id="u86690648" id="u86690648"><span data-lake-id="u982b9c67" id="u982b9c67">在这个查询中，</span><code data-lake-id="uc2dacb23" id="uc2dacb23"><span data-lake-id="u74d6e992" id="u74d6e992">userInputUsername</span></code><span data-lake-id="uf3a18b5b" id="uf3a18b5b"> 和 </span><code data-lake-id="u30f587fc" id="u30f587fc"><span data-lake-id="u3555064b" id="u3555064b">userInputPassword</span></code><span data-lake-id="u107c6e7d" id="u107c6e7d"> 是从用户输入中获取的值。如果应用程序不正确处理这些输入，它可能容易受到SQL注入攻击。</span></p>
  <p data-lake-id="u3d23b247" id="u3d23b247"><br></p>
  <p data-lake-id="u5ced6673" id="u5ced6673"><span data-lake-id="u742bc9fd" id="u742bc9fd">考虑以下情况，攻击者在用户名和密码字段中输入了恶意的字符串：</span></p>
  <p data-lake-id="u38113e93" id="u38113e93"><br></p>
  <pre lang="java"><code>
Username: ' OR '1'='1' --
Password: ' OR '1'='1' --
</code></pre>
  <p data-lake-id="u52db0a74" id="u52db0a74"><br></p>
  <p data-lake-id="ufab8d6f9" id="ufab8d6f9"><span data-lake-id="u6fc95d06" id="u6fc95d06">将这些值代入原始的SQL查询，得到的查询变成了：</span></p>
  <p data-lake-id="uff63ea03" id="uff63ea03"><br></p>
  <pre lang="java"><code>
SELECT * FROM users WHERE username='' OR '1'='1' --' AND password='' OR '1'='1' --
</code></pre>
  <p data-lake-id="u96558156" id="u96558156"><br></p>
  <p data-lake-id="ud9088ea4" id="ud9088ea4"><span data-lake-id="u039fb2a5" id="u039fb2a5">在注入的SQL中，使用</span><code data-lake-id="uc3856f79" id="uc3856f79"><span data-lake-id="uc855231b" id="uc855231b">--</span></code><span data-lake-id="u4332e579" id="u4332e579"> 来注释掉他后面的代码，那么我们原来的查询，就会返回用户表中的所有记录，因为 </span><code data-lake-id="uecd7c3ad" id="uecd7c3ad"><span data-lake-id="u3e522260" id="u3e522260">'1'='1'</span></code><span data-lake-id="u3d241611" id="u3d241611"> 是一个始终为true的条件。</span></p>
  <p data-lake-id="u337467ad" id="u337467ad"><br></p>
  <p data-lake-id="uf797156a" id="uf797156a"><span data-lake-id="u89958765" id="u89958765">如上，攻击者可以通过注入这样的恶意字符串绕过身份验证，获得对应用程序中所有用户的访问权限，甚至执行其他恶意操作。</span></p>
  <p data-lake-id="u217a106b" id="u217a106b"><span data-lake-id="u41a8607d" id="u41a8607d">​</span><br></p>
  <p data-lake-id="u08af8c8f" id="u08af8c8f"><span data-lake-id="u639c1d3d" id="u639c1d3d">如果还只是查询的话影响还不大，万一是一个delete操作被注入了，就可能会导致数据被攻击而导致删除。如以下被注入后的SQL：</span></p>
  <p data-lake-id="u223da9c0" id="u223da9c0"><span data-lake-id="ue0e225f4" id="ue0e225f4">​</span><br></p>
  <pre lang="java"><code>
DELETE FROM users WHERE username='' OR 1=1; --'
</code></pre>
  <p data-lake-id="u81103f6d" id="u81103f6d"><br></p>
  <h1 data-lake-id="DQkNa" id="DQkNa"><span data-lake-id="u282b5723" id="u282b5723">扩展知识</span></h1>
  <h2 data-lake-id="VwVgd" id="VwVgd"><span data-lake-id="u218f204b" id="u218f204b">如何防止被SQL注入</span></h2>
  <p data-lake-id="u2dc44914" id="u2dc44914"><br></p>
  <p data-lake-id="u35a9bbbb" id="u35a9bbbb"><strong><span data-lake-id="u665014f3" id="u665014f3">使用预编译语句：</span></strong><span data-lake-id="u5d5e2b02" id="u5d5e2b02"> 使用预编译的语句或参数化的语句，而不是通过字符串拼接构建SQL查询。这样可以防止攻击者通过在用户输入中插入恶意代码来改变SQL查询的结构。 </span></p>
  <p data-lake-id="u6d9efa4d" id="u6d9efa4d"><span data-lake-id="u37e35387" id="u37e35387">​</span><br></p>
  <p data-lake-id="u54c0eb6c" id="u54c0eb6c"><span data-lake-id="u2b5f24ab" id="u2b5f24ab">​</span><br></p>
  <p data-lake-id="uc5e4b512" id="uc5e4b512"><span data-lake-id="u26f68cf4" id="u26f68cf4">如使用JDBC的时候，使用PreparedStatement而不是Statement</span></p>
  <p data-lake-id="ud8469514" id="ud8469514"><span data-lake-id="u05c1c8df" id="u05c1c8df">​</span><br></p>
  <pre lang="java"><code>
// 错误的例子（容易受到SQL注入攻击）：
String userInput = "admin'; DROP TABLE users;--";
String query = "SELECT * FROM users WHERE username='" + userInput + "'";

// 正确的例子（使用预编译语句）：
String userInput = "admin'; DROP TABLE users;--";
String query = "SELECT * FROM users WHERE username=?";
PreparedStatement preparedStatement = connection.prepareStatement(query);
preparedStatement.setString(1, userInput);
</code></pre>
  <p data-lake-id="u3704ad87" id="u3704ad87"><span data-lake-id="u79b3e16c" id="u79b3e16c"> </span></p>
  <p data-lake-id="u3aa67f77" id="u3aa67f77"><strong><span data-lake-id="u2db17ebd" id="u2db17ebd">使用ORM框架：</span></strong><span data-lake-id="ue03fee6f" id="ue03fee6f"> 除了JDBC以外，我们基本都是使用Hibernate或MyBatis这种ORM框架，他们都可以自动处理SQL查询，减少手动拼接SQL的机会。 </span></p>
  <p data-lake-id="u8aca6914" id="u8aca6914"><span data-lake-id="u5635087c" id="u5635087c">​</span><br></p>
  <p data-lake-id="ue0fd888d" id="ue0fd888d"><span data-lake-id="ud3139222" id="ud3139222">在MyBatis中优先使用 #{} 语法而非${}语法，在 MyBatis 中，</span><code data-lake-id="ucda804c8" id="ucda804c8"><span data-lake-id="uba3326ce" id="uba3326ce">#{}</span></code><span data-lake-id="u0a064325" id="u0a064325">语法会进行预编译，而</span><code data-lake-id="ud78cb12d" id="ud78cb12d"><span data-lake-id="u05bbf263" id="u05bbf263">${}</span></code><span data-lake-id="u84f6475b" id="u84f6475b">语法是直接将参数的值拼接到 SQL 中，容易受到 SQL 注入攻击。因此，尽可能的使用 </span><code data-lake-id="ub28eb1d0" id="ub28eb1d0"><span data-lake-id="ued7d796b" id="ued7d796b">#{}</span></code><span data-lake-id="u1cabf7b6" id="u1cabf7b6">语法。</span></p>
  <p data-lake-id="uc00be9ea" id="uc00be9ea"><br></p>
  <p data-lake-id="u2bd1e5ad" id="u2bd1e5ad"><br></p>
  <p data-lake-id="u82f652d8" id="u82f652d8"><strong><span data-lake-id="uc3ec5524" id="uc3ec5524">用户输入校验：</span></strong><span data-lake-id="ua52101ed" id="ua52101ed">永远不要相信用户的输入，我们需要对用户输入进行验证和过滤，确保只有预期的数据被传递给数据库。使用正则表达式或其他合适的方法来检查输入的合法性。 </span></p>
  <p data-lake-id="u95a2056f" id="u95a2056f"><span data-lake-id="u1aa5e8e1" id="u1aa5e8e1">​</span><br></p>
  <pre lang="java"><code>
// 例子：使用正则表达式验证输入是否为合法的用户名
String userInput = request.getParameter("username");

if (userInput.matches("^[a-zA-Z0-9]+$")) {
    // 输入合法，继续处理
} else {
    // 输入非法，拒绝处理
}
</code></pre>
  <p data-lake-id="u5bd02a28" id="u5bd02a28"><span data-lake-id="ub017c9b1" id="ub017c9b1"> </span></p>
  <p data-lake-id="uaa954f57" id="uaa954f57"><strong><span data-lake-id="ub23ca5c1" id="ub23ca5c1">最小权限原则：</span></strong><span data-lake-id="u44cc5fdc" id="u44cc5fdc"> 为数据库用户分配最小必要的权限，以限制潜在的损害。不要使用具有过高权限的数据库账户连接数据库。 </span></p>
  <p data-lake-id="u95d8761e" id="u95d8761e"><span data-lake-id="uf10531ea" id="uf10531ea">​</span><br></p>
  <p data-lake-id="uba70aafa" id="uba70aafa"><strong><span data-lake-id="u602946fc" id="u602946fc">错误消息处理：</span></strong><span data-lake-id="uc76b5992" id="uc76b5992"> 避免向用户泄露敏感信息，例如详细的数据库错误消息。应该是封装成错误码返回到前端，而不是直接把报错的细节返回回去。比如那种唯一性约束冲突之类的，这些系统的内部设计不要报漏出来。</span></p>
  <p data-lake-id="u07fc3e19" id="u07fc3e19"><span data-lake-id="ub32bc6b0" id="ub32bc6b0">综合使用这些方法可以显著提高应用程序对SQL注入攻击的防御能力。</span></p>
 </body>
</html>